package kpi.javatraining6.gnatiuk.src.tester.readers;

import kpi.javatraining6.gnatiuk.src.tester.Logger;
import kpi.javatraining6.gnatiuk.src.tester.question.AnsweredQuestion;
import kpi.javatraining6.gnatiuk.src.tester.user.User;
import kpi.javatraining6.gnatiuk.src.tester.user.UserProgress;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import java.io.File;
import java.io.IOException;

public class UserDOMSaver {
    private Document doc;
    private XPath xpath;
    private User user;
    private static UserDOMSaver userDOMSaver;


    public static UserDOMSaver getInstance() {
        if (userDOMSaver == null) {
            userDOMSaver = new UserDOMSaver();
        }
        return userDOMSaver;
    }

    private UserDOMSaver() {
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = domFactory.newDocumentBuilder();
            doc = builder.parse("users.xml");
        } catch (ParserConfigurationException | SAXException | IOException e) {
            Logger.getInstance().recordExceptionalEvent(e);
            System.out.println("cant create parser");
            System.exit(1);
        }
        XPathFactory factory = XPathFactory.newInstance();
        xpath = factory.newXPath();
    }


    public void saveFile() {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        try {
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(doc);
            StreamResult result = new StreamResult(new File("users.xml"));
            transformer.transform(source, result);
        } catch (TransformerException e) {
            Logger.getInstance().recordExceptionalEvent(e);
            System.out.println("file not saved");
            System.exit(1);
        }
    }

    public void saveUserProgress(User user) {
        update();
        this.user = user;
        try {
            Node map = removeUserProgress();
            addUserProgress(map);
        } catch (XPathExpressionException e) {
            Logger.getInstance().recordExceptionalEvent(e);
            System.out.println("progress not saved");
            System.exit(1);
        }
        saveFile();
    }

    private void addUserProgress(Node map) throws XPathExpressionException {
        for (String topicTitle: user.getStartedTopics()) {
            map.appendChild(doc.createElement("progressItem"));
            addTopicNameToProgressItem(topicTitle);
            addQuestionsToProgressItem(topicTitle) ;
        }
    }

    private void addQuestionsToProgressItem(String topicTitle) throws XPathExpressionException {
        String xPathExpression = String.format("//user[userName='%s']/userProgressMap/progressItem[last()]", user.getUserName());
        XPathExpression expr = xpath.compile(xPathExpression);
        Node questions = doc.createElement("questionsList");
        Node progressItem = (Node)expr.evaluate(doc, XPathConstants.NODE);
        progressItem.appendChild(questions);
        addQuestions(topicTitle, questions);
    }

    private void addQuestions(String topicTitle, Node questions) {
        UserProgress userProgress =  user.getCurrentProgress(topicTitle);
        for (int questionIndex = 0; questionIndex < userProgress.getAnsweredQuestionsNumber(); questionIndex++) {
            Element question = doc.createElement("question");
            AnsweredQuestion answeredQuestion = userProgress.getAnsweredQuestion(questionIndex);
            question.appendChild(doc.createTextNode(answeredQuestion.getQuestion()));
            question.setAttribute("isCorrectAnswer", Boolean.toString(answeredQuestion.isRightAnswer()));
            questions.appendChild(question);
        }
    }

    private void addTopicNameToProgressItem(String topicTitle) throws XPathExpressionException {
        String xPathExpression = String.format("//user[userName='%s']/userProgressMap/progressItem[last()]", user.getUserName());
        XPathExpression expr = xpath.compile(xPathExpression);
        Node topicName = doc.createElement("topicName");
        topicName.appendChild(doc.createTextNode(topicTitle));
        Node progressItem = (Node)expr.evaluate(doc, XPathConstants.NODE);
        progressItem.appendChild(topicName);
    }

    private Node removeUserProgress() throws XPathExpressionException {
        String xPathExpression = String.format("//user[userName='%s']/userProgressMap", user.getUserName());
        XPathExpression expr = xpath.compile(xPathExpression);
        Node map = (Node)expr.evaluate(doc, XPathConstants.NODE);
        if (map == null) {
            map = recordUser();
        }
        while (map.hasChildNodes()) {
            map.removeChild(map.getFirstChild());
        }
        return map;
    }

    private Node recordUser() {
        Element root = doc.getDocumentElement();
        Element userElem = doc.createElement("user");
        root.appendChild(userElem);
        userElem.appendChild(createUserName());
        userElem.appendChild(createUserPassword());
        Node map = createProgressMap();
        userElem.appendChild(map);
        return map;
    }

    private Element createProgressMap() {
        return doc.createElement("userProgressMap");
    }

    private Element createUserName() {
        Element userName = doc.createElement("userName");
        Node userLogin = doc.createTextNode(user.getUserName());
        userName.appendChild(userLogin);
        return userName;
    }

    private Element createUserPassword() {
        Element userPassword = doc.createElement("password");
        Node password = doc.createTextNode(user.getPassword());
        userPassword.appendChild(password);
        return userPassword;
    }

    private void update() {
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = domFactory.newDocumentBuilder();
            doc = builder.parse("users.xml");
        } catch (ParserConfigurationException | IOException | SAXException e) {
            Logger.getInstance().recordExceptionalEvent(e);
        }
    }

}

